package bplustree;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.util.LinkedList;
import java.util.ListIterator;

import bplusfile.BPlusTreeFile;

import register.Register;
import register.RegisterID;

public class BPlusTree {
	private String path;
	private	BPlusTreeFile file;
	private int node_size;
	private int max_level;
	private LinkedList<Integer> free_positions;
	private Node superRoot;
	private int position;
	private RegisterIterator iterator;
	private int rootPosition;
		
	/**
	 * OJO NECESITA FINALIZARSE PARA GRABAR BIEN EL CHECKSUM
	 * 
	 * 
	 * @param path
	 * @throws IOException
	 */
	
	public BPlusTree(String path) throws IOException{
		 
		this.path=path;
		max_level=1;
		node_size=4096;
		Node root=new Node(max_level,this);
		free_positions=new LinkedList<Integer>();
		free_positions.add(3); //la primera posicion libre va a ser la 3, bplus, nodo y leaf ya asignadas
		position=0;
		superRoot =new Node (max_level+1,this);
		
		//TODO verificar que la inicialización sea correcta.
		
		try {
			
			file=new BPlusTreeFile(path,"r",getSeparatorAttribute(),getSeparatorField());
			
        } catch (FileNotFoundException e) {
        	//si el archivo no existe es un nuevo arbol
        	file=new BPlusTreeFile(path,"rw",getSeparatorAttribute(),getSeparatorField());
        	root=new Node(max_level,this);
        	root.setPosition(1); //Si no se setea a mano falla por el checksum, se cuelga todo
        	superRoot.writeChild(root);
        	rootPosition=root.getPosition();
    			//System.out.println(rootPosition);
        	Leaf tempLeaf=new Leaf(this);
        	tempLeaf.setPosition(2);
    		root.writeChild(tempLeaf);
    			//System.out.println(tempLeaf.getPosition());
    		root.childrenList.add(tempLeaf.getPosition());
    		tempLeaf.setNextLeaf(tempLeaf.getPosition());
    		tempLeaf.setPreviousLeaf(tempLeaf.getPosition());
    		superRoot.writeChild(root);
    			//System.out.println(rootPosition);
    		root.writeChild(tempLeaf);
    			//System.out.println(tempLeaf.getPosition());
    		try {
    			write();
    		} catch (IOException e2) {
    			System.err.println("FileStreams: " + e2);
    		}
    		return; //se creo el archivo nuevo
        }
        //si llega aca es porque el archivo existe y hay que cargarlo
        file=new BPlusTreeFile(path,"rw",getSeparatorAttribute(),getSeparatorField());
        try {
			load();
		} catch (BadChecksumException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        root=superRoot.loadChild(rootPosition);
                
	}
	
	/**
	 * Constructor con argumentos default
	 * @throws IOException 
	 * @throws BadChecksumException 
	 *
	 */
	public BPlusTree() throws IOException{
		this("arbol.txt");
	}
	
	
	public void finalize() {
		try {
			System.out.println("Saliendoooo, CHAUU");
			write();
			file.close();
		} catch (IOException e) {
			System.err.println("FileStreams: " + e);
		}
    }
	
	/**
	 * persists the node
	 * CANNOT USE MORE THAN NODE_SIZE
	 * @throws IOException 
	 *
	 */
	
	public void write() throws IOException{
		String check=Long.toString(calcChecksum());
		file.seek(0);
		file.writeBytes(
				  path
				+ getSeparatorField()
				+ node_size
				+ getSeparatorField()
				+ rootPosition
				+ getSeparatorField()
				+ max_level
				+ getSeparatorField()
				+ check
				+ getSeparatorField());
		ListIterator<Integer> iter=free_positions.listIterator();
		while(iter.hasNext()){
			file.writeBytes(iter.next().toString()+getSeparatorAttribute());
		}
		file.writeBytes(getSeparatorField()+"");
		while(file.getFilePointer()%node_size!=0) file.writeBytes("-"); //rellena con espacios
	}
	
	public void export(){
		
	}
	
	public void insert(register.Register reg) throws IOException{
		Node root=superRoot.loadChild(rootPosition);
		try {			
			root.insert(reg);
		} catch (NodeOverflowException e) {
			
			NodeOverflowHandler(root);
						
		} catch (DuplicateRegisterException e) {
			modify(reg);
		}
	}
	
	private void NodeOverflowHandler(Node root) throws IOException{
		Node tempNode=new Node(max_level,this);
		max_level++;
		Node tempRoot=new Node(max_level,this);
		superRoot.setLevel(max_level+1);
		
		while(tempNode.getFree_space()>tempNode.getNode_size()/2){
			tempNode.setFirstReg(root.removeLastReg());
			tempNode.setFirstChild(root.removeLastChild());
		}
		
		Register tempReg = tempNode.removeFirstReg();
		superRoot.writeChild(tempNode);
					
		tempRoot.registerList.add(tempReg);
		tempRoot.childrenList.add(root.getPosition());
		tempRoot.childrenList.add(tempNode.getPosition());
		superRoot.writeChild(tempRoot);
		rootPosition=tempRoot.getPosition();
		write();//modificacion del rootPosition
	}
	
	public void list() throws IOException{
		//System.out.println("Nivel del superroot = "+ superRoot.level);
		
		iterator=superRoot.loadChild(rootPosition).firstRegIterator();
		//System.out.println("Nivel del superroot = "+ superRoot.level);
		RegisterID lastID =iterator.previous().getID();
		iterator.next(); //descarto uno
		Register Prev=null;
		File file=new File("list.txt");
		if(file.exists()&&file.isFile()) file.delete();
		
		try{
		    // Create file 
			PrintWriter out
			   = new PrintWriter(new BufferedWriter(new FileWriter("list.txt")));
				
			out.println("Size; ID||At1|At2|At...");
			do{	
				Prev=iterator.next();
				System.out.println("Listando ID = " + Prev.getID().getID());
				out.println(Prev.toStringList());
				
			}while(Prev.getID().compareTo(lastID)!=0);
			//			Close the output stream
			out.close();
	    }catch (Exception e){//Catch exception if any
	      System.err.println("Error: " + e.getMessage());
	    }
		
	}
	
	public void load() throws IOException, BadChecksumException{
		file.seek(0);
		path=file.readField();
		node_size=Integer.valueOf(file.readField());
		rootPosition=Integer.valueOf(file.readField());
		max_level=Integer.valueOf(file.readField());
		
		long check = Long.valueOf(file.readField());
		try {
			compChecksum(check);
		} catch (BadChecksumException e) {
			System.err.println("BAD TREE CHECKSUM IN " + path);
			System.err.println("CHECKSUM EXPECTED   " + check);
			System.err.println("CHECKSUM CALCULATED " + calcChecksum());
			finalize();
			throw e;
		}
		
		String[] subStrings=file.readField().split(""+getSeparatorAttribute());
		free_positions.clear(); //Must clear before load
		
		for(int i=0; i<subStrings.length; i++){
			free_positions.addLast(Integer.valueOf(subStrings[i]));
		}
	}
	
	public void  modify(Register reg) throws IOException{
		Node root=superRoot.loadChild(rootPosition);
		try {			
			root.modify(reg);
		} catch (NodeOverflowException e) {
			
			NodeOverflowHandler(root);
						
		}
	}
	
	public void remove(){
		
	}
	
	/**
	 * Devuelve el registro con el ID especificado o en su defecto el inmediatamente menor
	 * Inicializa el iterador para recorrido secuencial
	 *  
	 * @param ID
	 * @return
	 * @throws IOException 
	 */
	
	public Register search(RegisterID ID) throws IOException{
		return superRoot.loadChild(rootPosition).search(ID,iterator);		
	}

	/**
	 * @return the first free position
	 * @throws IOException 
	 */
	public int getFree_positions() throws IOException {
		int temp =free_positions.removeFirst();
		if(free_positions.isEmpty()) free_positions.add(temp+1);
		//si no quedan libres, la primera libre es la siguiente a la que se fue recien
		write();
		return temp;
	}

	/**
	 * @param free_positions the _position to set after a node was removed
	 */
	public void setFree_positions(int free_position) {
		ListIterator<Integer> iter=free_positions.listIterator();
		while(iter.hasNext()){
			if(iter.next()>free_position){
				iter.previous();
				iter.add(free_position);
				return;
			}
		}
	}

	/**
	 * @return the node_size
	 */
	public int getNode_size() {
		return node_size;
	}

	/**
	 * @param node_size the node_size to set
	 */
	public void setNode_size(int node_size) {
		this.node_size = node_size;
	}
	
	public char getSeparatorAttribute(){
		return '!';
	}
	
	public char getSeparatorField(){
		return '@';
	}

	/**
	 * @return the file
	 */
	public BPlusTreeFile getFile() {
		return file;
	}

	/**
	 * @param file the file to set
	 */
	public void setFile(BPlusTreeFile file) {
		this.file = file;
	}
	
	public long calcChecksum() throws IOException{
		long pos=getFile().getFilePointer();
		getFile().seek(1*node_size); //starts in node 1, only checks data nodes
		long output=0;
		int aux=file.read();
		while(aux!=-1){
			//System.out.println("Calculando Check " + aux);
			output+=(char)aux;	
			aux=file.read();
		}
		getFile().seek(pos);
		return output;
	}
	
	public void compChecksum(long check) throws IOException, BadChecksumException{
		if (check!=calcChecksum()) throw new BadChecksumException();
	}
	
	
	
	
}
